package org.etnaframework.module.jdbc;

import org.etnaframework.module.base.utils.DbMap;
import org.etnaframework.module.base.utils.EnumWithCode;
import org.etnaframework.module.base.utils.ReflectUtils;
import org.etnaframework.module.jdbc.exception.BeanProcessException;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 根据bean的字段生成SQL辅助类
 *
 * @author jasyaf
 * @since 2023-12-27
 */
class BeanSqlMeta {

    /**
     * 目标bean对应的class
     */
    Class<?> clazz;

    /**
     * 目标bean中的字段的获取值方法
     */
    List<BeanFieldValueGetter> getters;

    BeanSqlMeta(Class<?> clazz, List<BeanFieldValueGetter> getters) {
        this.clazz = clazz;
        this.getters = getters;
    }

    /**
     * 将传入Map<String,Object>中的基本型字段按照XX=aa, YY=bb这样的方式添加到sql中去
     */
    static void addEqualsBeanSql(String quoteString, SqlBuilder sql, Map<String, ?> map) {
        for (Entry<String, ?> e : map.entrySet()) {
            Object value = e.getValue();
            if (null != value) {
                sql.add(quoteString + e.getKey() + quoteString + "=?, ", value);
            }
        }
        sql.removeTail();
    }

    /**
     * 将传入Map<String,Object>中的基本型字段按照(XX, YY, ZZ) values (aa, bb, cc)的方式添加到sql中去
     */
    static void addInsertBeanSql(String quoteString, SqlBuilder sql, Map<String, ?> map) {
        int count = 0;
        StringBuilder sb = new StringBuilder();
        for (Entry<String, ?> e : map.entrySet()) {
            Object value = e.getValue();
            sb.append(quoteString).append(e.getKey()).append(quoteString).append(", ");
            sql.addArg(value);
            count++;
        }
        if (count > 0) {
            sb.deleteCharAt(sb.length() - 1).deleteCharAt(sb.length() - 1);
            sql.add("(" + sb + ")");
            sql.add("values").add(sql.getMarks(count));
        }
    }

    static BeanSqlMeta create(Class<?> clazz) {
        if (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)) {
            throw new BeanProcessException("Collection type " + clazz.getName() + " is not supported");
        }
        if (DbMap.isPrimitiveWrapperType(clazz)) {
            throw new BeanProcessException("Primitive wrapper type" + clazz.getName() + " is not supported");
        }
        // 如果有这个field，首先找其get方法，如果找不到，就将直接通过反射取值
        Collection<Field> fields = ReflectUtils.getAllFieldsInSourceCodeOrder(clazz, null);

        List<BeanFieldValueGetter> list = new ArrayList<>(fields.size());
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) {
                continue;
            }
            DbField cc = field.getAnnotation(DbField.class);
            boolean writeToDb = true;
            String name = field.getName();
            boolean readFromDb = true;
            boolean writeNullToDb = false;
            EnumConvertMode enumConvertMode = EnumConvertMode.NAME;
            if (null != cc) { // 如果没有加注解就直接使用类字段名作为数据库字段名
                CharSequence str = cc.name();
                name = str == null || str.length() == 0 ? field.getName() : cc.name();
                writeToDb = cc.writeToDb();
                readFromDb = cc.readFromDb();
                writeNullToDb = cc.writeNullToDb();
                enumConvertMode = cc.enumConvertMode();
            }
            if (!writeToDb) {
                continue;
            }
            Class<?> fieldType = field.getType();
            boolean isEnum = fieldType.isEnum();
            if (!isEnum && DbMap.isNotPrimitiveWrapperType(fieldType)) {
                throw new BeanProcessException(clazz.getName() + "." + field.getName() + " must be 1.Enum types 2.Primitive wrapper type defined in " + DbMap.class.getName() + ". If you don't want to assign a value to it，add @" + DbField.class.getSimpleName() + " and set writeToDb=false");
            }
            BeanFieldValueGetter getter = BeanFieldValueGetter.create(name, readFromDb, writeNullToDb, clazz, field, enumConvertMode);
            list.add(getter);
        }
        if (list.isEmpty()) { // 空类（即没有任何字段的类）是不允许的，会抛出异常
            throw new BeanProcessException("Class " + clazz.getName() + " cannot be empty, it must have at least one field");
        }
        return new BeanSqlMeta(clazz, list);
    }

    /**
     * 生成 select 字段名 的sql
     *
     * @param tableName 表名，用于添加类似 t.id 的sql，如果传入null就不生成表名字段
     */
    void addSelectSql(String quoteString, SqlBuilder sql, String tableName) {
        boolean add = false;
        StringBuilder builder = new StringBuilder("select ");
        if (null == tableName || tableName.isEmpty()) {
            for (BeanFieldValueGetter g : getters) {
                if (g.readFromDb) {
                    builder.append(quoteString).append(g.name).append(quoteString).append(", ");
                    add = true;
                }
            }
        } else {
            for (BeanFieldValueGetter g : getters) {
                if (g.readFromDb) {
                    builder.append(quoteString).append(tableName).append(quoteString).append(".").append(quoteString).append(g.name).append(quoteString).append(", ");
                    add = true;
                }
            }
        }
        if (add) {
            sql.add(builder.toString());
            sql.removeTail();
        }
    }

    /**
     * 将传入bean中的非空基本型字段按照XX=aa, YY=bb这样的方式添加到sql中去
     */
    void addEqualsBeanSql(String quoteString, SqlBuilder sql, Object bean) {
        boolean add = false;
        for (BeanFieldValueGetter g : getters) {
            Object value = g.getValue(bean);
            if (null != value || g.writeNullToDb) { // 只要非空就添加sql，或者是设置了允许null添加
                sql.add(quoteString + g.name + quoteString + "=?,", value);
                add = true;
            }
        }
        if (add) {
            sql.removeTail();
        }
    }

    /**
     * 将传入bean中的非空基本型字段按照(XX, YY, ZZ) values (aa, bb, cc)的方式添加到sql中去
     */
    void addInsertBeanSql(String quoteString, SqlBuilder sql, Object bean) {
        int count = 0;
        StringBuilder mid = new StringBuilder();
        StringBuilder tail = new StringBuilder();
        for (BeanFieldValueGetter g : getters) {
            Object value = g.getValue(bean);
            if (null != value || g.writeNullToDb) { // 只要非空就添加sql，或者是设置了允许null添加
                mid.append(quoteString).append(g.name).append(quoteString).append(", ");
                tail.append("?, ");
                sql.addArg(value);
                count++;
            }
        }
        if (count > 0) {
            mid.deleteCharAt(mid.length() - 1).deleteCharAt(mid.length() - 1);
            tail.deleteCharAt(tail.length() - 1).deleteCharAt(tail.length() - 1);
            sql.add("(" + mid + ")");
            sql.add("values(" + tail + ")");
        }
    }

    static abstract class BeanFieldValueGetter {

        /**
         * 对应的数据库表字段名
         */
        String name;

        /**
         * 是否参与生成select语句
         */
        boolean readFromDb;

        /**
         * 当字段值为null时，是否参与sql生成
         */
        boolean writeNullToDb;

        BeanFieldValueGetter(String name, boolean readFromDb, boolean writeNullToDb) {
            this.name = name;
            this.readFromDb = readFromDb;
            this.writeNullToDb = writeNullToDb;
        }

        /**
         * 生成一个取值工具，用于给指定JavaBean的指定字段取值
         * 规则是如果有get方法就调用get方法，否则直接用反射取值
         */
        static BeanFieldValueGetter create(String name, boolean readFromDb, boolean writeNullToDb, Class<?> beanClass, Field field, EnumConvertMode enumConvertMode) {
            final Field f = field;
            final Method m = ReflectUtils.getGetMethodByField(beanClass, f);

            boolean useGetter;
            if (null != m) {
                useGetter = true;
                m.setAccessible(true);
            } else {
                useGetter = false;
                f.setAccessible(true);
            }

            if (f.getType().isEnum()) {
                switch (enumConvertMode) {
                    case NAME:
                        return new BeanFieldValueGetter(name, readFromDb, writeNullToDb) {

                            @Override
                            public Object getValue(Object bean) {
                                Object value = useGetter ? ReflectUtils.invokeMethod(m, bean) : ReflectUtils.getFieldValue(f, bean);
                                if (null != value) {
                                    Enum<?> e = DbMap.toEnumByName((Class<? extends Enum>) f.getType(), String.valueOf(value), null);
                                    if (null == e) {
                                        throw new IllegalArgumentException("value=" + value + " cannot be converted to enum type " + f.getType().getName());
                                    }
                                }
                                return value;
                            }
                        };
                    case ORDINAL:
                        return new BeanFieldValueGetter(name, readFromDb, writeNullToDb) {

                            @Override
                            public Object getValue(Object bean) {
                                Object value = useGetter ? ReflectUtils.invokeMethod(m, bean) : ReflectUtils.getFieldValue(f, bean);
                                if (null != value) {
                                    Integer valueInt = DbMap.toInt(value.toString(), null);
                                    if (null == valueInt) {
                                        throw new IllegalArgumentException("value=" + value + " is not an ordinal, cannot be converted to enum type " + f.getType().getName());
                                    }
                                    Enum<?> e = DbMap.toEnumByOrdinal((Class<? extends Enum>) f.getType(), valueInt, null);
                                    if (null == e) {
                                        throw new IllegalArgumentException("value=" + value + " cannot be converted to enum type " + f.getType().getName());
                                    }
                                }
                                return value;
                            }
                        };
                    case CODE:
                        if (!f.getType().isAssignableFrom(EnumWithCode.class)) {
                            throw new IllegalArgumentException("class " + beanClass.getName() + "'s field " + f.getName() + " is not " + EnumWithCode.class.getSimpleName() + ", you cannot use mode " + EnumConvertMode.CODE);
                        }
                        return new BeanFieldValueGetter(name, readFromDb, writeNullToDb) {

                            @Override
                            public Object getValue(Object bean) {
                                Object value = useGetter ? ReflectUtils.invokeMethod(m, bean) : ReflectUtils.getFieldValue(f, bean);
                                if (null != value) {
                                    EnumWithCode<?> e = DbMap.toEnumByCode((Class<? extends EnumWithCode>) f.getType(), value.toString(), null);
                                    if (null == e) {
                                        throw new IllegalArgumentException("value=" + value + " cannot be converted to enum type " + f.getType().getName());
                                    }
                                }
                                return value;
                            }
                        };
                }
            }

            return new BeanFieldValueGetter(name, readFromDb, writeNullToDb) {

                @Override
                public Object getValue(Object bean) {
                    if (useGetter) {
                        return ReflectUtils.invokeMethod(m, bean);
                    }
                    return ReflectUtils.getFieldValue(f, bean);
                }
            };
        }

        /**
         * 获取指定对象的字段的值，获取失败或出现异常将返回null
         */
        public abstract Object getValue(Object bean);
    }
}
